#!/usr/bin/env php
<?php
$SOAR_DIR=getenv("SOAR_DIR");
if(empty($SOAR_DIR)) $SOAR_DIR="/opt/soar";
include "$SOAR_DIR/common/php/soar/soar.inc";

ini_set("memory_limit", "512M");

class share
{/*{{{*/
    public static $stat_lock;
    public static $stat;

    public static $operate_lock;
    public static $operate;
}/*}}}*/

function load_cnfg()
{/*{{{*/
    $svcenv_dir=getenv("SVCENV_DIR");
    if(empty($svcenv_dir))
    {
        print "SVCENV_DIR not set\n";
        return false;
    }
    $svcenv_cnfg=getenv("SVCENV_CNFG");
    if(empty($svcenv_cnfg))
    {
        print "SVCENV_CNFG not set\n";
        return false;
    }
    if(!file_exists($svcenv_cnfg))
    {
        printf("SVCENV_CNFG(%s) not found\n", $svcenv_cnfg);
        return false;
    }
    include $svcenv_cnfg;

    if(!isset(cnfg::$agent))
    {
        print "SVCENV_CNFG.agent not set\n";
        return false;
    }
    if(!isset(cnfg::$agent['workers']) ||
            cnfg::$agent['workers'] < 1 || cnfg::$agent['workers'] > 32)
    {
        cnfg::$agent['workers']=4;
        printf("SVCENV_CNFG.agent.workers forced to be %d\n",
                cnfg::$agent['workers']);
    }

    if(!isset(cnfg::$agent['server_port']) ||
            cnfg::$agent['server_port'] < 1024)
    {
        print "SVCENV_CNFG.agent.server_port invalid\n";
        return false;
    }
    if(!isset(cnfg::$agent['monitor_port']) ||
            cnfg::$agent['monitor_port'] < 1024)
    {
        print "SVCENV_CNFG.agent.monitor_port invalid\n";
        return false;
    }

    // errlog
    if(errlog::init(basename(__FILE__), "$svcenv_dir/log") == false)
    {
        print "init errlog fail\n";
        return false;
    }

    return true;
}/*}}}*/

class svctrack
{/*{{{*/
    private $begin;
    private $end;
    private $track;

    public function __construct()
    {/*{{{*/
        $this->begin=$this->end=gettimeofday();
        $this->track=array();
    }/*}}}*/

    public function mark($svc)
    {/*{{{*/
        $now=gettimeofday();
        $this->track[]=array('begin'=>$now,'end'=>$now,'svc'=>$svc);
    }/*}}}*/

    public function over()
    {/*{{{*/
        $this->track[count($this->track)-1]['end']=gettimeofday();
    }/*}}}*/

    public function dump()
    {/*{{{*/
        $this->end=gettimeofday();
        $buf=sprintf("use %d ms, begin(%s)->",
                misc::diff_millisecond($this->begin, $this->end),
                strftime("%H%M%S", $this->begin['sec']));
        foreach($this->track as $t)
        {
            $buf.=sprintf("%s(use:%d %s-%s)->",
                    $t['svc'],
                    misc::diff_millisecond($t['begin'], $t['end']),
                    strftime("%H%M%S", $t['begin']['sec']),
                    strftime("%H%M%S", $t['end']['sec']));
        }
        $buf.=sprintf("end(%s)", strftime("%H%M%S", $this->end['sec']));
        return $buf;
    }/*}}}*/
}/*}}}*/

class worker
{/*{{{*/
    public static $worker_idx;

    private static $svcnode=array();

    public function get_svcnode($snname)
    {/*{{{*/
        if(!isset(self::$svcnode[$snname]))
        {
            errlog::add("%s: svcnode(%s) not in cnfg",
                    __METHOD__, $snname);
            return false;
        }
        return self::$svcnode[$snname];
    }/*}}}*/

    private static $dbconn=array();

    public function get_dbconn($dbname)
    {/*{{{*/
        if(!array_key_exists($dbname, cnfg::$db))
        {
            errlog::add("%s: %s not in cnfg",
                    __METHOD__, $dbname);
            return false;
        }
        if(array_key_exists($dbname, self::$dbconn))
            return self::$dbconn[$dbname];

        $dbcnfg=cnfg::$db[$dbname];
        if(empty($dbcnfg['passwd']))
        {
            if(!isset(cnfg::$svcnode) || !isset(cnfg::$svcnode['passwd']))
            {
                errlog::add("%s: no sn_passwd available\n", __METHOD__);
                return false;
            }
            $passwd=svcnode_call::getdbpasswd(
                    cnfg::$svcnode['passwd']['addr'],
                    $dbname, $dbcnfg['user']);
            if($passwd === false)
            {
                errlog::add("%s: get db(%s) passwd fail",
                        __METHOD__, $dbname);
                return false;
            }
            elseif(empty($passwd))
            {
                errlog::add("%s: db(%s) passwd empty",
                        __METHOD__, $dbname);
                return false;
            }
        }
        else
        {
            $passwd=$dbcnfg['passwd'];
        }

//        $dsn=sprintf("%s:host=%s;dbname=%s;charset=UTF-8",
//                $dbcnfg['type'], $dbcnfg['host'], $dbcnfg['database']);
//        try
//        {
//            $conn=new PDO($dsn, $dbcnfg['user'], $passwd);
//            $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//        }
//        catch(PDOException $e)
//        {
//            errlog::add("%s: connect to db(%s) fail: %s",
//                    __METHOD__, $dbname, $e->getMessage());
//            return false;
//        }
//        self::$dbconn[$dbname]=$conn;

        switch($dbcnfg['type'])
        {
            case "mysql":
            {
                $conn=mysqli_init();
                $conn->options(MYSQLI_INIT_COMMAND, "set names utf8");
                if($conn->real_connect($dbcnfg['host'], $dbcnfg['user'],
                            $passwd, $dbcnfg['database'], 0, null,
                             MYSQLI_CLIENT_FOUND_ROWS) == false)
                {
                    errlog::add("%s: connect to db(%s) fail:%s",
                            __METHOD__, $dbname,
                             mysqli_connect_errno());
                    return false;
                }
                self::$dbconn[$dbname]=$conn;
                break;
            }
            case "oci8":
            {
                $conn=oci_connect($dbcnfg['user'], $passwd,
                        isset($dbcnfg['host'])?
                        $dbcnfg['host']:'localhost');
                if($conn === false)
                {
                    $error=oci_error();
                    errlog::add("%s: connect to db(%s) fail:%d %s",
                            __METHOD__, $dbname,
                            $error['code'], $error['message']);
                    return false;
                }
                self::$dbconn[$dbname]=$conn;
                break;
            }

            case "mssql":
            {
                $conn=mssql_connect($dbcnfg['host'], $dbcnfg['user'],
                        $passwd);
                if($conn === false)
                {
                    errlog::add("%s: connect to db(%s) fail",
                            __METHOD__, $dbname);
                    return false;
                }
                if(mssql_select_db($dbcnfg['database'], $conn) == false)
                {
                    errlog::add("%s: select db(%s) fail",
                            __METHOD__, $dbcnfg['database']);
                    return false;
                }
                self::$dbconn[$dbname]=$conn;
                break;
            }
            default:
            {
                errlog::add("%s: database not supported", __METHOD__);
                $conn=false;
                break;
            }
        }

        return $conn;
    }/*}}}*/

    private function close_dbconn()
    {/*{{{*/
        foreach(self::$dbconn as $name=>$conn)
        {
            $dbcnfg=cnfg::$db[$name];
            //            self::$dbconn[$name]=null;
            switch($dbcnfg['type'])
            {
                case "mysql":
                {
                    $conn->close();
                    break;
                }
                case "oci8":
                {
                    oci_close($conn);
                    break;
                }
                case "mssql":
                {
                    mssql_close($conn);
                    break;
                }
                default:
                {
                    break;
                }
            }
            unset(self::$dbconn[$name]);
        }
    }/*}}}*/

    private static $memcache=array();

    public function get_memcache($mcname)
    {/*{{{*/
        if(!isset(cnfg::$memcache[$mcname]))
        {
            errlog::add("%s: memcache(%s) not in cnfg",
                    __METHOD__, $mcname);
            return false;
        }
        if(isset(self::$memcache[$mcname]))
        {
            $conn=self::$memcache[$mcname];
            if($conn->getVersion())
                return self::$memcache[$mcname];
            $conn->close();
            unset(self::$memcache[$mcname]);
            errlog::add("%s: memcache(%s) down, reconnect",
                    __METHOD__, $mcname);
        }

        $mccnfg=cnfg::$memcache[$mcname];
        $arr=split(':', $mccnfg['addr']);
        $conn=new Memcache();
        if($conn->connect($arr[0], $arr[1]) == false)
        {
            errlog::add("%s: connect memcache(%s) fail",
                    __METHOD__, $mcname);
            return false;
        }
        self::$memcache[$mcname]=$conn;

        return $conn;
    }/*}}}*/

    private function close_memcache()
    {/*{{{*/
        foreach(self::$memcache as $name=>$conn)
        {
            $conn->close();
            unset(self::$memcache[$name]);
        }
    }/*}}}*/

    private static $seq=0;
    private static $svc_call_count=array();

    private function call_svcnode($request, $svctrack)
    {/*{{{*/
        // SVCNODE
        if(!isset($request['node']) ||
                strlen($request['node']) > misc::NAME_SIZE)
        {
            errlog::add("%s: worker(%d) svcnode(%s) invalid",
                    __METHOD__, self::$worker_idx,
                    isset($request['node'])?$request['node']:'null');
            $response=array('errcode'=>SOAR_ERR::PARA);
            return $response;
        }
        $snname=strtolower($request['node']);
        $sn=self::get_svcnode($snname);
        if($sn === false)
        {
            errlog::add("%s: svcnode(%s) not found",
                    __METHOD__, $snname);
            $response=array('errcode'=>SOAR_ERR::SVC_NOT_FOUND);
            return $response;
        }

        // SVC
        if(!isset($request['svc']) ||
                strlen($request['svc']) > misc::NAME_SIZE)
        {
            errlog::add("%s: worker(%d) svc(%s) invalid",
                    __METHOD__, self::$worker_idx,
                    isset($request['svc'])?$request['svc']:'null');
            $response=array('errcode'=>SOAR_ERR::SVC_INVALID);
            return $response;
        }
        $svc=strtoupper($request['svc']);

        $svctrack->mark($svc);

        $res=$sn->call($svc, $request['paras']);
        if($res === false)
        {
            errlog::add("%s: worker(%d): sn(%s) call fail",
                    __METHOD__, self::$worker_idx, $snname);
            $response=array('errcode'=>SOAR_ERR::COMM_FAIL);
        }
        else
        {
            $response=$res;
        }

        $svctrack->over();

        return $response;
    }/*}}}*/

    private function call_svcenv($request, $svctrack)
    {/*{{{*/
        if(!isset($request['svc']) ||
                strlen($request['svc']) > misc::NAME_SIZE)
        {
            errlog::add("%s: worker(%d) svc(%s) invalid",
                    __METHOD__, self::$worker_idx,
                    isset($request['svc'])?$request['svc']:'null');
            $response=array('errcode'=>SOAR_ERR::SVC_INVALID);
            return $response;
        }
        $svc=strtoupper($request['svc']);

        $svctrack->mark($svc);

        if(class_exists($svc))
        {
            $ins=new $svc();
            $response=$ins->call($request['paras']);
            if(array_key_exists($svc, self::$svc_call_count))
                ++self::$svc_call_count[$svc];
            else
                self::$svc_call_count[$svc]=1;
            if($response === false)
            {
                self::close_dbconn();
                $response=array('errcode'=>SOAR_ERR::DB);
            }
        }
        else
        {
            errlog::add("%s: worker(%d) func(%s) not found",
                    __METHOD__, self::$worker_idx, $svc);
            $response=array('errcode'=>SOAR_ERR::SVC_NOT_FOUND);
        }

        $svctrack->over();

        return $response;
    }/*}}}*/

    private function proc($new_sock)
    {/*{{{*/
        static $TIMEOUT=60;

        if(++self::$seq == 10000) self::$seq=0;

        $svctrack=new svctrack();

        $abort=false;
        $end=false;
        while(!$end)
        {
            $to=$TIMEOUT;
            $res=sock::recv($new_sock, $request, $to);
            if($res == false)
            {
                errlog::add("%s: worker(%d) sock::recv fail",
                        __METHOD__, self::$worker_idx);
                $abort=true;
                break;
            }
            //errlog::add("%s: request:%s", __METHOD__, $request);
            $request=json_decode($request, true);

            if(!isset($request['cmd']))
            {
                errlog::add("%s: worker(%d) cmd null",
                        __METHOD__, self::$worker_idx);
                $response=array('errcode'=>SOAR_ERR::CMD_INVALID); 
            }
            else
            {
                $cmd=strtoupper($request['cmd']);
                switch($cmd)
                {
                    case 'CLOSE':
                    {
                        $response=array('errcode'=>SOAR_ERR::OK); 
                        $end=true;
                        break;
                    }
                    case 'CALL':
                    {
                        $response=self::call_svcenv($request, $svctrack);
                        break;
                    }
                    case 'CALLNODE':
                    {
                        $response=self::call_svcnode($request, $svctrack);
                        break;
                    }
                    default:
                    {
                        errlog::add("%s: worker(%d) %s not CALL or ".
                                "CALLNODE",
                                __METHOD__, self::$worker_idx, $cmd);
                        $response=array('errcode'=>SOAR_ERR::CMD_INVALID); 
                        break;
                    }
                }
            }

            $response=json_encode($response);
            $to=$TIMEOUT;
            $res=sock::send($new_sock, $response, $to);
            if($res == false)
            {
                errlog::add("%s: worker(%d) sock::send fail",
                        __METHOD__, self::$worker_idx);
                $abort=true;
                break;
            }
        }

        if($abort)
        {
            errlog::add("%s: worker(%d) sess(%d) %s",
                    __METHOD__, self::$worker_idx, self::$seq,
                    $svctrack->dump());
        }
    }/*}}}*/

    private function loop($listen_sock)
    {/*{{{*/
        $TIMEOUT=2;
        $UPDATE_STAT_INFO_INTERVAL=10;
        $CHECK_OPERATE_INFO_INTERVAL=2;

        $last_stat=0;
        $last_operate=0;
        $client_conn_count=0;

        $quit=false;
        while(!$quit)
        {
            $to=$TIMEOUT;
            $new_sock=sock::accept($listen_sock, $to);
            if($new_sock === false)
            {
                if($to != 0)
                {
                    if(socket_last_error() != SOCKET_EAGAIN)
                    {
                        errlog::add("%s: worker(%d) sock accept fail",
                                __METHOD__, self::$worker_idx);
                    }
                }
                else
                {
                    //errlog::add("%s: worker(%d) accept timeout",
                    //        __METHOD__, self::$worker_idx);
                }
            }
            else
            {
                //printf("worker(%d) new client:%d\n",
                //        self::$worker_idx, $new_sock);
                self::proc($new_sock);
                socket_close($new_sock);
                ++$client_conn_count;
            }

            $now=time();

            if($now-$last_stat > $UPDATE_STAT_INFO_INTERVAL)
            {/*{{{*/
                if($client_conn_count > 0)
                {
                    $client_call_count=0;

                    sem_acquire(share::$stat_lock);
                    $stat=shm_get_var(share::$stat, 0);

                    foreach(self::$svc_call_count as $name=>$count)
                    {
                        if($count > 0)
                        {
                            if(array_key_exists($name, $stat['svc']))
                                $stat['svc'][$name]+=$count;
                            else
                                $stat['svc'][$name]=$count;
                            $client_call_count+=$count;
                        }
                    }
                    self::$svc_call_count=array();

                    $stat['worker'][self::$worker_idx]['client_conn_num']+=
                        $client_conn_count;
                    $stat['worker'][self::$worker_idx]['client_call_num']+=
                        $client_call_count;

                    shm_put_var(share::$stat, 0, $stat);
                    sem_release(share::$stat_lock);

                    $client_conn_count=0;
                }

                $last_stat=$now;
            }/*}}}*/

            if($now-$last_operate > $CHECK_OPERATE_INFO_INTERVAL)
            {/*{{{*/
                sem_acquire(share::$operate_lock);
                $operate=shm_get_var(share::$operate, 0);

                $quit=$operate['quit'];
                if($quit)
                {
                    $operate['worker'][self::$worker_idx]='STOP';
                    shm_put_var(share::$operate, 0, $operate);
                    errlog::add("%s: worker(%d) quit(%d)",
                            __METHOD__, self::$worker_idx, $quit);
                }

                sem_release(share::$operate_lock);

                $last_operate=$now;
            }/*}}}*/
        }

        // 清理
        self::$svcnode=array();
        self::close_dbconn();
        self::close_memcache();
    }/*}}}*/

    public function start($listen_sock)
    {/*{{{*/
        $svc_dir=getenv('SVCENV_DIR')."/bin/svc";
        include_once "$svc_dir/svr.inc";
        $dh=opendir($svc_dir);
        while($inc=readdir($dh))
        {
            if(substr($inc, 0, 1) != "." &&
                    substr($inc, -4) == ".inc" &&
                    $inc != "svr.inc")
            {
                include_once "$svc_dir/$inc";
                printf("load $inc\n");
            }
        }

        for($i=0; $i<cnfg::$agent['workers']; ++$i)
        {
            $pid=pcntl_fork();
            if($pid == -1)
            {
                printf("%s: fork workers fail\n", __METHOD__);
                return false;
            }
            if($pid == 0)
            {
                self::$worker_idx=$i;
                if(isset(cnfg::$svcnode))
                {
                    foreach(cnfg::$svcnode as $snname=>$sncnfg)
                    {
                        self::$svcnode[$snname]=new svcnode(
                                $sncnfg['addr'],
                                isset($sncnfg['mode']) ?
                                $sncnfg['mode'] : svcnode::ONESHOT);
                    }
                }
                printf("worker(%d) start\n", $i);
                self::loop($listen_sock);
                exit(0);
            }
            monitor::$worker[$i]=array('pid'=>$pid, 'alive'=>true);
        }

        return true;
    }/*}}}*/
}/*}}}*/

class monitor
{/*{{{*/
    public static $worker=array();

    private static $quit=false;
    private static $cmd_map=array(
            array(
                'name'=>"printworker", 'ab'=>"pwrk",
                'desc'=>"print client ip & port",
                'usage'=>"printclient(pwrk)"
                ),
            array(
                'name'=>"printservice", 'ab'=>"psvc",
                'desc'=>"print service status",
                'usage'=>"printservice(psvc)"
                ),
            array(
                'name'=>"shutdown", 'ab'=>"shutdown",
                'desc'=>"shutdown server",
                'usage'=>"shutdown"
                ),
            array(
                'name'=>"help", 'ab'=>"h",
                'desc'=>"",
                'usage'=>""
                )
                );

    private function CMD_FUNC_PRINTWORKER($request)
    {/*{{{*/
        $response=
            "Worker         Connected        Calls          Status\n".
            "------         ---------        -----          ------\n";
        $buf=array();

        sem_acquire(share::$stat_lock);
        $stat=shm_get_var(share::$stat, 0);
        for($i=0; $i<cnfg::$agent['workers']; ++$i)
        {
            $worker=$stat['worker'][$i];
            $buf[$i]=sprintf("%6d         %9d    %9d",
                    $i,
                    $worker['client_conn_num'],
                    $worker['client_call_num']);
        }
        sem_release(share::$stat_lock);

        sem_acquire(share::$operate_lock);
        $operate=shm_get_var(share::$operate, 0);
        for($i=0; $i<cnfg::$agent['workers']; ++$i)
        {
            $buf[$i].=sprintf("          %s", $operate['worker'][$i]);
        }
        sem_release(share::$operate_lock);

        for($i=0; $i<cnfg::$agent['workers']; ++$i)
        {
            $response.=$buf[$i]."\n";
        }

        return $response;
    }/*}}}*/

    private function CMD_FUNC_PRINTSERVICE($request)
    {/*{{{*/
        $response=
            "Service                         Calls\n".
            "-------                         ---------\n";
        sem_acquire(share::$stat_lock);
        $stat=shm_get_var(share::$stat, 0);
        foreach($stat['svc'] as $name=>$num)
        {
            $response.=sprintf("%-30s  %9d\n",
                    $name, $num);
        }
        sem_release(share::$stat_lock);
        return $response;
    }/*}}}*/

    private function CMD_FUNC_SHUTDOWN($request)
    {/*{{{*/
        sem_acquire(share::$operate_lock);
        $operate=shm_get_var(share::$operate, 0);
        $operate['quit']=true;
        shm_put_var(share::$operate, 0, $operate);
        sem_release(share::$operate_lock);

        sleep(3);

        for($i=0; $i<5; ++$i)
        {
            $finish=true;
            sem_acquire(share::$operate_lock);
            $operate=shm_get_var(share::$operate, 0);
            for($j=0; $j<cnfg::$agent['workers']; ++$j)
            {
                $worker=$operate['worker'][$j];
                if($worker == 'RUNNING')
                {
                    $finish=false;
                    break;
                }
            }
            sem_release(share::$operate_lock);
            if($finish)
            {
                $response="Shutdown complete\nDisconnect\n";
                self::$quit=true;
                return $response;
            }
            errlog::add("%s: try %d fail", __METHOD__, $i+1);
            sleep(2);
        }
        $response="Shutdown not complete, try again\n";
        return $response;
    }/*}}}*/

    private function CMD_FUNC_HELP($request)
    {/*{{{*/
        if(empty($request))
        {
            $response="help [command]\ncommands:\n";
            foreach(self::$cmd_map as $arr)
            {
                $response.="    ".$arr['name']."\n";
            }
            return $response;
        }

        $request=strtolower($request);
        foreach(self::$cmd_map as $arr)
        {
            if($arr['name'] == $request || $arr['ab'] == $request)
            {
                $response="Description: ".$arr['desc']."\n".
                    "Usage: ".$arr['usage']."\n";
                return $response;
            }
        }
        $response="Command not found, type help\n";
        return $response;
    }/*}}}*/

    private function cmd($request)
    {/*{{{*/
        $found=false;
        foreach(self::$cmd_map as $arr)
        {
            if($arr['name'] == $cmd || $arr['ab'] == $cmd)
            {
                $found=true;
                $cmd=$arr['name'];
                break;
            }
        }
        if($found)
        {
            $func="CMD_FUNC_".$cmd;
            $response=self::$func($body);
        }
        else
        {
            $response="command(".$cmd.") not found\n";
        }

        return $response;
    }/*}}}*/

    private function scan_worker()
    {/*{{{*/
        for($i=0; $i<cnfg::$agent['workers']; ++$i)
        {
            $worker=&self::$worker[$i];
            if($worker['alive'])
            {
                $pid=pcntl_waitpid($worker['pid'], $status, WNOHANG);
                //printf("$pid: $status\n");
                if($pid > 0)
                {
                    sem_acquire(share::$operate_lock);
                    $operate=shm_get_var(share::$operate, 0);
                    if($operate['worker'][$i] == 'RUNNING')
                    {
                        $operate['worker'][$i]='ABORT';
                        shm_put_var(share::$operate, 0, $operate);
                        errlog::add("%s: worker(%d) abort",
                                __METHOD__, $i);
                    }
                    sem_release(share::$operate_lock);

                    $worker['alive']=false; 
                }
            }
        }
    }/*}}}*/

    public function start($monitor_sock)
    {/*{{{*/
        $TIMEOUT=5;

        while(!self::$quit)
        {
            $to=$TIMEOUT;
            $console_sock=sock::accept($monitor_sock, $to);
            if($console_sock === false)
            {
                continue;
            }

            $console_quit=false;
            while(!self::$quit && !$console_quit)
            {
                self::scan_worker();

                $res=socket_recv($console_sock, $request, 500, 0);
                if($res === false || $res == 0)
                {
                    errlog::add("%s: read console fail", __METHOD__);
                    $console_quit=true;
                    continue;
                }

                $request=trim($request);
                $pos=strpos($request, " ");
                if($pos === false)
                {
                    $cmd=$request;
                    $body="";
                }
                else
                {
                    $cmd=substr($request, 0, $pos);
                    $body=substr($request, $pos);
                    $body=ltrim($body);
                }
                $cmd=strtolower($cmd);
                //printf("cmd(%s)\n", $cmd);
                $cmdsize=strlen($cmd);
                if(0 < $cmdsize && $cmdsize <= misc::NAME_SIZE) 
                {
                    $found=false;
                    foreach(self::$cmd_map as $arr)
                    {
                        if($arr['name'] == $cmd || $arr['ab'] == $cmd)
                        {
                            $found=true;
                            $cmd=$arr['name'];
                            break;
                        }
                    }
                    if($found)
                    {
                        $func="CMD_FUNC_".$cmd;
                        $response=self::$func($body);
                    }
                    else
                    {
                        $response="command(".$cmd.") not found\n";
                    }
                }
                else
                {
                    $response="command invalid\n";
                }

                $res=socket_send($console_sock,
                        $response, strlen($response), 0);
                if($res === false || $res == 0)
                {
                    errlog::add("%s: write console fail", __METHOD__);
                    $console_quit=true;
                    continue;
                }
            }
            socket_close($console_sock);
        }

        errlog::add("%s: shutdown", __METHOD__);
        return true;
    }/*}}}*/
}/*}}}*/

function start()
{/*{{{*/
    pcntl_signal(SIGPIPE, SIG_IGN);

    $pid=pcntl_fork();
    if($pid == -1)
    {
        printf("%s: daemonize fail", __METHOD__);
        return false;
    }
    if($pid > 0)
    {
        return true;
    }

    // setuid
    if(posix_getuid() == 0)
    {/*{{{*/
        if(!empty(cnfg::$user))
        {
            $user=posix_getpwnam(cnfg::$user);
            if($user)
            {
                if(posix_setuid($user['uid']) == false)
                {
                    printf("Change to user %s fail\n", cnfg::$user);
                    return false;
                }
                printf("Change to user %s\n", cnfg::$user);
            }
            else
            {
                printf("User %s not found, keep root\n", cnfg::$user);
            }
        }
        else
        {
            printf("User empty, keep root\n");
        }
    }/*}}}*/

    // 通讯端口
    $monitor_sock=sock::listen(cnfg::$agent['monitor_port']);
    if($monitor_sock === false)
    {
        printf("Error: open monitor_port(%d) fail\n",
                cnfg::$agent['monitor_port']);
        return false;
    }
    $listen_sock=sock::listen(cnfg::$agent['server_port']);
    if($listen_sock === false)
    {
        printf("Error: open server_port(%d) fail\n",
                cnfg::$agent['server_port']);
        return false;
    }
    socket_set_nonblock($listen_sock);

    // 初始化共享信息
    $key=ftok(getenv('SVCENV_CNFG'), 's');
    if(($id=sem_get($key)) === false ||
            sem_remove($id) == false ||
            ($id=sem_get($key)) === false)
    {
        printf("Error: init sem(share) fail\n");
        return false;
    }
    share::$stat_lock=$id;
    if(($id=shm_attach($key)) === false ||
            shm_remove($id) == false ||
            ($id=shm_attach($key)) === false)
    {
        printf("Error: init shm(share) fail\n");
        return false;
    }
    for($i=0; $i<cnfg::$agent['workers']; ++$i)
        $stat['worker'][$i]=
            array('client_conn_num'=>0, 'client_call_num'=>0);
    $stat['svc']=array();
    shm_put_var($id, 0, $stat);
    share::$stat=$id;

    $key=ftok(getenv('SVCENV_CNFG'), 'o');
    if(($id=sem_get($key)) === false ||
            sem_remove($id) == false ||
            ($id=sem_get($key)) === false)
    {
        printf("Error: init sem(operate) fail\n");
        return false;
    }
    share::$operate_lock=$id;
    if(($id=shm_attach($key)) === false ||
            shm_remove($id) == false ||
            ($id=shm_attach($key)) === false)
    {
        printf("Error: init shm(operate) fail\n");
        return false;
    }
    for($i=0; $i<cnfg::$agent['workers']; ++$i)
        $operate['worker'][$i]='RUNNING';
    $operate['quit']=false;
    shm_put_var($id, 0, $operate);
    share::$operate=$id;

    if(worker::start($listen_sock) == false)
        return false;

    return monitor::start($monitor_sock);
}/*}}}*/

function main($argv)
{/*{{{*/
    if(load_cnfg() == false)
        return 1;
    if(start() == false)
        return 1;
    return 0;
}/*}}}*/

exit(main($argv));
?>
